#include <glf.h>
#include <glfwindow.h>
#include <glfu.h>
//#include <gl3.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>



static const float s_identityMatrix[16]={1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};

void glfuZeroMatrix( float *m )
{
	assert( m );
	if( m )
	{
		memset( m, 0, sizeof( float ) * 16 );
	}
}

void glfuCopyMatrix( float *m, const float *n )
{
	assert( m );
	assert( n );
	if( m && n )
	{
		memcpy( m, n, sizeof( float ) * 16 );
	}
}

void glfuIdentityMatrix( float *m )
{
	assert( m );
	if( m )
	{
		memcpy( m, s_identityMatrix, sizeof( float ) * 16 );
	}
}

void glfuTranslationMatrix( float *m, const float *v )
{
	assert( m );
	assert( v );
	if( m && v )
	{
		glfuIdentityMatrix( m );
		m[3]=v[0];
		m[7]=v[1];
		m[11]=v[2];
	}
}

void glfuRotationMatrixX( float *m, const float radians )
{
	assert( m );
	if( m )
	{
		glfuIdentityMatrix( m );
		m[5]=cosf( radians );
		m[6]=sinf( radians );
		m[9]=-sinf( radians );
		m[10]=cosf( radians );
	}
}

void glfuRotationMatrixY( float *m, const float radians )
{
	assert( m );
	if( m )
	{
		glfuIdentityMatrix( m );
		m[0]=cosf( radians );
		m[2]=-sinf( radians );
		m[8]=sinf( radians );
		m[10]=cosf( radians );
	}
}

void glfuRotationMatrixZ( float *m, const float radians )
{
	assert( m );
	if( m )
	{
		glfuIdentityMatrix( m );
		m[0]=cosf( radians );
		m[1]=sinf( radians );
		m[4]=-sinf( radians );
		m[5]=cosf( radians );
	}
}

void glfuMultiplyMatrix( float *m, const float *n )
{
	float r[16];

	int i, j;

	assert( m );
	assert( n );

	if( m && n )
	{

	/* Calculate the new matrix */
	for (i = 0; i < 4; i++)
		for (j = 0; j < 4; j++)
			r[j + i * 4] = m[i * 4 + 0] * n[j] + m[i * 4 + 1] * n[4 + j] + m[i * 4 + 2] * n[8 + j] + m[i * 4 + 3] * n[12 + j];

	/* Copy in the destination matrix */
	glfuCopyMatrix( m, r );

	}

}



// size is number of vector elements, for example 2 or 3 or 4
// v = v * m;

void glfuMultiplyVectorMatrix( float *v, const size_t size, const float *m )
{
#define M(row,col)  m[col*4+row]
	float r[4];
	int i;
	for(i=0;i<size;++i)
	{
		r[i] =	( M(i,0)*v[i] ) + 
				( M(i,1)*v[i] ) + 
				( M(i,2)*v[i] ) + 
				( M(i,3)*v[i] );
	}
#undef M
	memcpy( v, r, sizeof( float ) * size );
}


void glfuScaleMatrix( float *m, const float *v )
{
	assert( m );
	assert( v );
	if( m && v )
	{
		glfuZeroMatrix( m );
		m[0]=v[0];
		m[5]=v[1];
		m[10]=v[2];
		m[15]=1;
	}
}

void glfuOrthographicMatrix( float *m, const float left, const float right, const float bottom, const float top, const float zNear, const float zFar )	//far is a MSVC typename, use zFar instead
{
	if( m )
	{
#define M(row,col)  m[col*4+row]
		M(0,0) = 2.0f / (right-left);
		M(0,1) = 0.0f;
		M(0,2) = 0.0f;
		M(0,3) = -(right+left) / (right-left);
		M(1,0) = 0.0f;
		M(1,1) = 2.0f / (top-bottom);
		M(1,2) = 0.0f;
		M(1,3) = -(top+bottom) / (top-bottom);
		M(2,0) = 0.0f;
		M(2,1) = 0.0f;
		M(2,2) = -2.0f / (zFar-zNear);
		M(2,3) = -(zFar+zNear) / (zFar-zNear);
		M(3,0) = 0.0f;
		M(3,1) = 0.0f;
		M(3,2) = 0.0f;
		M(3,3) = 1.0f;
#undef M
	}
}

void glfuOrthographicMatrix2d( float *m, const float left, const float right, const float bottom, const float top )
{
	if( m )
	{
		glfuOrthographicMatrix( m, left, right, bottom, top, -1, 1 );
	}
}

void glfuPerspectiveMatrix( float *m, const float fovy, const float aspect, const float zNear, const float zFar)
{

	float sine, cotangent, deltaZ;
	float radians = fovy / 2 * M_PI / 180;

	deltaZ = zFar - zNear;
	sine = sinf(radians);
	if ((deltaZ == 0) || (sine == 0) || (aspect == 0)) {
		return;
	}
	cotangent = cosf(radians) / sine;

	glfuIdentityMatrix( m );

#define M(row,col)  m[col*4+row]
	M(0,0) = cotangent / aspect;
	M(1,1) = cotangent;
	M(2,2) = -(zFar + zNear) / deltaZ;
	M(2,3) = -1;
	M(3,2) = -2 * zNear * zFar / deltaZ;
	M(3,3) = 0;
#undef M


}

static void normalize(float v[3])
{
	float r;

	r = sqrtf( v[0]*v[0] + v[1]*v[1] + v[2]*v[2] );
	if (r == 0.0f) return;

	v[0] /= r;
	v[1] /= r;
	v[2] /= r;
}

static void cross(float v1[3], float v2[3], float result[3])
{
	result[0] = v1[1]*v2[2] - v1[2]*v2[1];
	result[1] = v1[2]*v2[0] - v1[0]*v2[2];
	result[2] = v1[0]*v2[1] - v1[1]*v2[0];
}

void glfuLookAtMatrix( float *m, const float eyex, const float eyey, const float eyez, const float centerx,
		  const float centery, const float centerz, const float upx, const float upy,
		  const float upz)
{
	float forward[3], side[3], up[3];
	float v[3];
	float n[16];

	forward[0] = centerx - eyex;
	forward[1] = centery - eyey;
	forward[2] = centerz - eyez;

	up[0] = upx;
	up[1] = upy;
	up[2] = upz;

	normalize(forward);

	/* Side = forward x up */
	cross(forward, up, side);
	normalize(side);

	/* Recompute up as: up = side x forward */
	cross(side, forward, up);

	glfuIdentityMatrix( m );

#define M(row,col)  m[col*4+row]
	M(0,0) = side[0];
	M(1,0) = side[1];
	M(2,0) = side[2];

	M(0,1) = up[0];
	M(1,1) = up[1];
	M(2,1) = up[2];

	M(0,2) = -forward[0];
	M(1,2) = -forward[1];
	M(2,2) = -forward[2];
#undef M

	v[0]=-eyex;
	v[1]=-eyey;
	v[2]=-eyez;
	glfuTranslationMatrix( n, v );
	glfuMultiplyMatrix( m, n );
}

int glfuTraceShaderInfoLog( const int handle )
{
	char string[1024];
	string[1023]=0;
	glGetShaderInfoLog( handle, 1023, 0, string );
	if( string[0] != 0 )
	{
		GLF_TRACE( "Shader error: %s\n", string );
		return -1;
	}
	return 0;
}

int glfuLoadShader( const int e, const char *filename )
{
	const GLint handle = glCreateShader( e );
	FILE *f = NULL;
	long filesize = 0;
	char *source = NULL;
	char *s[1]={0};
	if( glIsShader( handle ) == GL_TRUE )
	{
		f = fopen(filename, "rb");
		if(!f){
			fprintf(stderr, "Error in function in LoadShaderSource: Couldn't open file %s for reading.\n",filename);
			return 0;
		}
		fseek(f, 0, SEEK_END);
		filesize = ftell(f);
		rewind(f);
		source = malloc( sizeof(char) * (filesize) + 1);

		if(!source){
			fprintf(stderr, "Error in function in LoadShader: Out of memory.\n");
			fclose(f);
			return 0;
		}

		fread(source, sizeof(char), filesize, f);
		source[ sizeof(char) * (filesize) ] = 0;
		fclose(f);
		glShaderSource(handle, 1, &source, &filesize);
		free( source );
		glCompileShader( handle );
		if( glfuTraceShaderInfoLog( handle ) == 0 )
		{
			return handle;
		}
		glDeleteShader( handle );
	}
	return 0;
}

int glfuTraceProgramInfoLog( const int handle )
{
	char string[1024];
	string[1023]=0;
	glGetProgramInfoLog( handle, 1023, 0, string );
	if( string[0] != 0 )
	{
		GLF_TRACE( "Program error: %s\n", string );
		return -1;
	}
	return 0;
}

int glfuUnloadProgram( const int handle )
{
	if( glIsProgram( handle ) == GL_TRUE )
	{
		glDeleteProgram( handle );
		return 0;
	}
	return -1;
}

int glfuLoadProgram( const int *attributes )
{
	int handle = 0;
	int shaderHandle = 0;
	GLenum type = 0;
	char *filename = 0;
	handle = glCreateProgram();
	while( *attributes )
	{
		if( *attributes == GL_VERTEX_SHADER || *attributes == GL_FRAGMENT_SHADER )
		{
			type = *attributes++;
			filename = (char*)*attributes++;
			shaderHandle = glfuLoadShader( type, filename );
			glAttachShader( handle, shaderHandle );
			glDeleteShader( shaderHandle );	//Mark shader for deletion and GL will delete it with the program							
		}	
		else
		{
			glfuUnloadProgram( handle );
			return 0;
		}
	}
	glLinkProgram( handle );
	if( glfuTraceProgramInfoLog( handle ) != 0 )
	{
		glfuUnloadProgram( handle );
		return 0;
	}
	return handle;
}

/*int glfuLoadTextureData2dTga( glfuTextureData2d_t *data, const char *filename )
{
	static const unsigned int rgba = 0xFF0000FF;
	if( data )
	{
		data->width = 1;
		data->height = 1;
		data->format = GL_RGBA;
		data->data = &rgba;
		data->size = 4;
		return 0;
	}
	return -1;
}*/

int glfuUnloadTextureData2d( glfuTextureData2d_t *data )
{
if( data )
{
	free( data->data );
	data->width = 0;
	data->height = 0;
	data->format = 0;
	data->data = 0;
	data->size = 0;
	return 0;
}
return -1;
}
